#include "Logger.h"
#include "CloudDiskServer.h"
#include "CryptoUtil.h"



#include <nlohmann/json.hpp>
#include <workflow/MySQLResult.h>

#include <map>
#include <string>
#include <vector>
#include <utility>
#include <fstream>
#include <functional>

using namespace wfrest;
using namespace protocol;
using JSON = nlohmann::json;
using std::map;
using std::string;
using std::vector;
using std::ofstream;


namespace
{
    //mysql的url
    constexpr const char* MYSQL_URL = "mysql://root:123@127.0.0.1/CloudDisk";
    //保存前端上传文件的路径
    constexpr const char* DEFAULT_FILE_SAVE_PATH = "./resources/";


    /*==============================================================================================*/
    /*===================================辅助函数===================================================*/
    /*==============================================================================================*/

    //从query中解析出一个key对应的val
    bool parse_query_by_key(const HttpReq* req, string& val, const string& key) {

        if (!req->has_query(key)) {
#ifdef BUG
            std::cout << "BadRequest : query not have " << "key: " << key << "\n";
#endif
            return false;
        }

        val = req->query(key);

        return true;
    }

    // 从query中解析出1个key对应的val
    bool parse_query_by_key(const HttpReq* req, string& val1, string& val2, const string& key1, const string& key2) {

        if (!req->has_query(key1) || !req->has_query(key2)) {
#ifdef BUG
            std::cout << "BadRequest : query not have " << "key1: " << key1 << " key2: " << key2 << "\n";
#endif
            return false;
        }

        val1 = req->query(key1);
        val2 = req->query(key2);
        return true;

    }

    //从application/x-www-form-urlencoded格式的表单中用两个key解析出两个val
    bool parse_body_on_form_kv(const HttpReq* req, string& val, const string& key) {

        if (req->content_type() != APPLICATION_URLENCODED) {
#ifdef BUG
            std::cout << "BadRequest content is not APPLICATION_URLENCODED\n";
#endif
            return false;
        }

        //解析key
        map<string, string> formData{ req->form_kv() };

        //如果二者有一个不存在，就算失败，是一个BadRequest
        if (!formData.count(key)) {
#ifdef BUG
            std::cout << "BadRequest : body not have " << "key: " << key << "\n";
#endif
            return false;
        }

        val = formData[key];
        return true;
    }


    //从application/x-www-form-urlencoded格式的表单中用两个key解析出两个val
    bool parse_body_on_form_kv(const HttpReq* req, string& val1, string& val2, const string& key1, const string& key2) {

        if (req->content_type() != APPLICATION_URLENCODED) {
#ifdef BUG
            std::cout << "BadRequest content is not APPLICATION_URLENCODED\n";
#endif
            return false;
        }

        //解析key1和key2
        map<string, string> formData{ req->form_kv() };

        //如果二者有一个不存在，就算失败，是一个BadRequest
        if (!formData.count(key1) || !formData.count(key2)) {
#ifdef BUG
            std::cout << "BadRequest : body not have " << "key1: " << key1 << " key2: " << key2 << "\n";
#endif
            return false;
        }

        val1 = formData[key1];
        val2 = formData[key2];
        return true;
    }

    //利用query传递username和password的方式的登录验证
    bool verify_login(const HttpReq* req, string& username, string& token) {

        if (!parse_query_by_key(req, username, token, "username", "token")) {
            return false;
        }

#ifdef BUG
        std::cout << "username: " << username << "\n";
        std::cout << "token: " << token << "\n";
#endif

        //此处可以对username和token检错提早返回
        bool is_login = l::CryptoUtil::verify_jwt(token.c_str());
        if (username.empty() || token.empty() || !is_login) {

            return false;
        }

        return true;
    }


    /*==============================================================================================*/
   /*==================================路由的回调中创建的任务=========================================*/
   /*==============================================================================================*/

   //登录的mysql回调
    void handlesiginMySQL(HttpResp* resp, string password, string username, MySQLResultCursor* cursor)
    {

        if (cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT || cursor->get_rows_count() == 0) {

            resp->set_status(HttpStatusBadRequest);
            resp->String("This user does not exist\n");
#ifdef BUG
            std::cout << "mysql failed This user does not exist" << "\n";
#endif
            return;
        }

        vector<MySQLCell> record{};
        cursor->fetch_row(record);
        string hashPassword{ record[0].as_string() };
        string salt{ record[1].as_string() };

        //验证
        if (l::CryptoUtil::verifyHashWithSalt(password, hashPassword, salt) == false) {
            resp->set_status(HttpStatusBadRequest);
            resp->String("Wrong password\n");
#ifdef BUG
            std::cout << " Wrong password\n" << "\n";
#endif
            return;
        }


        //生成token
        string token{ l::CryptoUtil::generate_jwt_token() };
        string data{};
        data.append(R"({"data":{"Token":")").append(token).append(R"(","Username":")").append(username);
        data.append(R"(","Location":"/static/view/home.html"}})");

        JSON js = data;
        // js["data"] = { {"Token", token}, {"Username", username}, {"Location", "home.html"} };
#ifdef BUG
        std::cout << "token: " << token << "\n";
        std::cout << "json:\n " << js.dump(2) << "\n";
#endif

        resp->Json(js.dump());

    }

    //info的mysql回调
    void handleInfoMySQL(HttpResp* resp, string username, MySQLResultCursor* cursor)
    {

        if (cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT || cursor->get_rows_count() == 0) {

            resp->set_status(HttpStatusBadRequest);
            resp->String("This user does not exist\n");
#ifdef BUG
            std::cout << "mysql failed This user does not exist\n" << "\n";
#endif
            return;
        }

        vector<MySQLCell> record{};
        cursor->fetch_row(record);
        string create_at{ record[0].as_string() };

        string data{};
        data.append(R"({"data":{"Username":")").append(username).append(R"(","SignupAt":")").append(create_at);
        data.append(R"("}})");

        JSON js(data);
#ifdef BUG
        std::cout << "json:\n " << js.dump(2) << "\n";
#endif

        resp->Json(js.dump());

    }

    //Query的mysql回调
    void handleQueryMySQL(HttpResp* resp, MySQLResultCursor* cursor)
    {

        if (cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT) {

            resp->set_status(HttpStatusInternalServerError);
#ifdef BUG
            std::cout << "mysql failed to select created_at" << "\n";
#endif
            return;
        }

        vector<MySQLCell> record{};
        string data{ R"([)" };

        for (int i = 0; cursor->fetch_row(record); i++) {

            string filename{ record[0].as_string() };
            string hashcode{ record[1].as_string() };
            string size{ std::to_string(record[2].as_ulonglong()) };
            string created_at{ record[3].as_string() };
            string last_update{ record[4].as_string() };

            data.append(R"({"FileHash":")").append(hashcode).append(R"(",)");
            data.append(R"("FileName":")").append(filename).append(R"(",)");
            data.append(R"("FileSize":")").append(size).append(R"(",)");
            data.append(R"("UploadAt":")").append(created_at).append(R"(",)");
            data.append(R"("LastUpdated":")").append(last_update).append(R"("},)");

        }
        data.erase(data.end() - 1);
        data.append("]");

        JSON js = data;
#ifdef BUG
        std::cout << "json:\n " << js.dump(2) << "\n";
#endif

        resp->Json(js.dump());

    }

    //Upload的mysql回调
    void handleUploadMySQL(HttpResp* resp, string hash_code, string fileContent, MySQLResultCursor* cursor)
    {

        //检查第一个select哈希值的结果集
        if (cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT) {

            resp->set_status(HttpStatusBadRequest);
            resp->String("filed to select\n");
#ifdef BUG
            std::cout << "mysql failed  select hash\n" << "\n";
#endif
            return;
        }
        else {

        }

        //如果文件哈希值不存在，说明没有保存过,就保存，反之不保存,用哈希值作为文件名
        if (cursor->get_rows_count() < 1) {

            string real_path{ DEFAULT_FILE_SAVE_PATH };
            real_path.append(hash_code);
            ofstream ofs{ real_path };
            if (!ofs) {
                resp->set_status(HttpStatusInternalServerError);
#ifdef BUG
                std::cout << real_path << ": failed open" << "\n";
#endif
            }

            ofs.write(fileContent.c_str(), fileContent.size());
#ifdef BUG
            std::cout << "hash not found save file" << "\n";
#endif
        }
        else {
#ifdef BUG
            std::cout << "hash founded not save file" << "\n";
#endif
        }

        //检查第二个insert的结果集
        cursor->next_result_set();


        if (cursor->get_cursor_status() == MYSQL_STATUS_OK) {

            //上传成功跳转到用户的首页
            resp->Redirect("/static/view/home.html", HttpStatusSeeOther);
#ifdef BUG
            std::cout << "mysql insert success" << "\n";
#endif
        }
        else {

            //上传失败代表后端出错了
            resp->set_status(HttpStatusInternalServerError);
            resp->String("500 Http Status Internal Server Error\n");
#ifdef BUG
            std::cout << "mysql insert failed" << "\n";
#endif
        }

    }


    /*==============================================================================================*/
    /*==================================路由的回调===================================================*/
    /*==============================================================================================*/

    void handleSignup(const HttpReq* req, HttpResp* resp) {

        string username{};
        string password{};
        if (!parse_body_on_form_kv(req, username, password, "username", "password")) {
            resp->set_status(HttpStatusBadRequest);
            return;
        }
#ifdef BUG
        std::cout << "username: " << username << "\n";
        std::cout << "password: " << password << "\n";
#endif

        //注册
        string hashPassword{};
        string salt{};
        l::CryptoUtil::hashWithSalt(hashPassword, salt, password);

        string url{ MYSQL_URL };
        string sql{ "INSERT INTO users (username, password, salt) VALUES(" };
        sql.append("\"").append(username).append("\",\"").append(hashPassword).append("\",\"").append(salt).append("\")");
#ifdef BUG
        std::cout << "sql: " << sql << "\n";
#endif

        resp->MySQL(url, sql, [resp](MySQLResultCursor* cursor)
            {

                if (cursor->get_cursor_status() == MYSQL_STATUS_OK) {
                    resp->String("SUCCESS");
#ifdef BUG
                    std::cout << "mysql success" << "\n";
#endif
                }
                else {
                    resp->set_status(HttpStatusBadRequest);
#ifdef BUG
                    std::cout << "mysql failed" << "\n";
#endif
                }

            });

    }

    void handleSignin(const HttpReq* req, HttpResp* resp) {

        LogInfo("login start");

        string username{};
        string password{};
        if (!parse_body_on_form_kv(req, username, password, "username", "password")) {
            resp->set_status(HttpStatusBadRequest);
            return;
        }
#ifdef BUG
        std::cout << "username: " << username << "\n";
        std::cout << "password: " << password << "\n";
#endif

        //登录，根据username查users表中的password和salt
        string url{ MYSQL_URL };
        string sql{ "SELECT password, salt FROM users WHERE username = \"" };
        sql.append(username).append("\" AND tomb = 0");
#ifdef BUG
        std::cout << "sql: " << sql << "\n";
#endif

        resp->MySQL(url, sql, std::bind(handlesiginMySQL, resp, std::move(password), std::move(username), std::placeholders::_1));

    }

    void handleInfo(const HttpReq* req, HttpResp* resp) {

        string username{};
        string token{};
        if (!verify_login(req, username, token)) {
            resp->Redirect("/static/view/signin.html", HttpStatusSeeOther);
#ifdef BUG
            std::cout << "verify login failed" << "\n";
#endif
            return;
        }

        string url{ MYSQL_URL };
        string sql{ "SELECT  created_at FROM users WHERE username = \"" };
        sql.append(username).append("\" AND tomb = 0");
#ifdef BUG
        std::cout << "sql: " << sql << "\n";
#endif


        resp->MySQL(url, sql, std::bind(handleInfoMySQL, resp, std::move(username), std::placeholders::_1));

    }

    void handleQuery(const HttpReq* req, HttpResp* resp) {

        string username{};
        string token{};
        if (!verify_login(req, username, token)) {
            resp->Redirect("/static/view/signin.html", HttpStatusSeeOther);
#ifdef BUG
            std::cout << "verify login failed" << "\n";
#endif
            return;
        }

        string limit{};
        if (!parse_body_on_form_kv(req, limit, "limit")) {
            resp->set_status(HttpStatusBadRequest);
            return;
        }
#ifdef BUG
        std::cout << "limit: " << limit << "\n";
#endif

        string url{ MYSQL_URL };
        string sql{ "SELECT  files.filename,files.hashcode, files.size, files.create_at, files.last_update"
                    " FROM users JOIN files ON users.id = files.uid WHERE users.username = \"" };
        sql.append(username).append("\" AND tomb = 0 LIMIT ").append(limit);
#ifdef BUG
        std::cout << "sql: " << sql << "\n";
#endif

        resp->MySQL(url, sql, std::bind(handleQueryMySQL, resp, std::placeholders::_1));

    }

    //上传
    void handleUpload(const HttpReq* req, HttpResp* resp) {

        //验证登录
        string username{};
        string token{};
        if (!verify_login(req, username, token)) {
            resp->Redirect("/static/view/signin.html", HttpStatusSeeOther);
#ifdef BUG
            std::cout << "verify login failed" << "\n";
#endif
            return;
        }

        if (req->content_type() != MULTIPART_FORM_DATA) {
            resp->set_status(HttpStatusBadRequest);
            return;
        }

        const Form& form = req->form();

        for (auto& [_, file] : form) {

            string filename{ file.first };
            string fileContent{ file.second };

            //计算哈希值，查找并插入file表
            string hash_code{};
            if (!l::CryptoUtil::hashFileContent(hash_code, fileContent)) {
                resp->set_status(HttpStatusBadGateway);
                return;
            }

#ifdef BUG
            std::cout << "filename: " << filename << "\n";
            std::cout << "fileContent: " << fileContent << "\n";
            std::cout << "hashcaode: " << hash_code << "\n";
#endif

            //查找这个哈希值在files中是否存在,先查询，后插入，以防多个用户同时先插入，查询结果误判，导致实际不保存
            //插入files表
            //拼成一个sql
            string url{ MYSQL_URL };
            //查找
            string sql{ "SELECT  hashcode"
                " FROM files WHERE hashcode = "
            };
            sql.append("\"").append(hash_code).append("\";");
            //插入
            sql.append("REPLACE INTO files (uid, filename, hashcode, size) SELECT users.id,");
            sql.append("\"").append(filename).append("\",\"").append(hash_code).append("\",")
                .append(std::to_string(fileContent.size())).append(" FROM users WHERE users.username = ")
                .append("\"").append(username).append("\" AND tomb = 0;");

#ifdef BUG
            std::cout << "sql: " << sql << "\n";
#endif


            resp->MySQL(url, sql, std::bind(handleUploadMySQL, resp, std::move(hash_code),
                std::move(fileContent), std::placeholders::_1));

        }
    }

    //下载的回调
    void handleDownload(const HttpReq* req, HttpResp* resp) {

        string username{};
        string token{};
        if (!verify_login(req, username, token)) {
            resp->Redirect("/static/view/signin.html", HttpStatusSeeOther);
#ifdef BUG
            std::cout << "verify login failed" << "\n";
#endif
            return;
        }

        string filename{};
        string filehash{};
        if (!parse_query_by_key(req, filename, filehash, "filename", "filehash")) {
            resp->set_status(HttpStatusBadRequest);
            return;
        }

#ifdef BUG
        std::cout << "filename: " << filename << "\n";
        std::cout << "filehash: " << filehash << "\n";
        std::cout << "username: " << username << "\n";
        std::cout << "token: " << token << "\n";
#endif

        //直接返回文件数据
        string real_path{ DEFAULT_FILE_SAVE_PATH };
        real_path.append(filehash);
        resp->File(real_path);

    }

}//end of Anonymity namespace 

namespace l
{

    //注册注册路由
    void CloudDiskServer::register_signup_module() {

        m_http_server.POST("user/signup", handleSignup);

    }

    //注册登录路由
    void CloudDiskServer::register_signin_module() {
        m_http_server.POST("user/signin", handleSignin);
    }

    //注册文件上传路由
    void CloudDiskServer::register_fileupload_module() {
        m_http_server.POST("file/upload", handleUpload);
    }

    //注册获取user信息路由
    void CloudDiskServer::register_userinfo_module() {
        m_http_server.GET("user/info", handleInfo);
    }



    //注册获取文件列表路由
    void CloudDiskServer::register_filelist_module() {
        m_http_server.POST("file/query", handleQuery);
    }

    //注册文件下载路由
    void CloudDiskServer::register_filedownload_module() {
        m_http_server.GET("file/download", handleDownload);
    }

    void CloudDiskServer::register_modules()
    {
        // 设置静态资源的路由
        register_static_resources_module();
        register_signup_module();
        register_signin_module();
        register_userinfo_module();
        register_filelist_module();
        register_fileupload_module();
        register_filedownload_module();
    }

    void CloudDiskServer::register_static_resources_module()
    {
        m_http_server.GET("/user/signup", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/view/signup.html");
            });

        m_http_server.GET("/static/view/signin.html", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/view/signin.html");
            });

        m_http_server.GET("/static/view/home.html", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/view/home.html");
            });

        m_http_server.GET("/static/js/auth.js", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/js/auth.js");
            });

        m_http_server.GET("/static/img/avatar.jpeg", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/img/avatar.jpeg");
            });

        m_http_server.GET("/file/upload", [](const HttpReq*, HttpResp* resp) {
            resp->File("static/view/index.html");
            });

        m_http_server.Static("/file/upload_files", "static/view/upload_files");
    }

}//end os namespaca l